﻿using System;
using System.Reflection;

namespace NearAop
{
    public class MethodArgs
    {
        private readonly Lazy<AopParamInfo> aopParam;

        public MethodArgs(Lazy<AopParamInfo> aopParam, object sender)
        {
            this.aopParam = aopParam;
            Sender = sender;
        }

        private ParamNameMapping NameMap => aopParam.Value.Mapping;

        public MethodInfo MethodInfo => aopParam.Value.MemberInfo as MethodInfo;

        /// <summary>
        /// 触发事件的对象
        /// </summary>
        public object Sender { get; }

        public Type GetSenderType() => Sender is Type type ? type : Sender.GetType();

        /// <summary>
        /// 方法参数名集合(只读集合)
        /// </summary>
        public string[] ParamNames => NameMap.ParamNames;

        public void OutValue() { }

        /// <summary>
        /// 方法返回值是否为Void
        /// </summary>
        public bool IsVoid => MethodInfo.ReturnType == typeof(void);

        #region virtuals

        protected virtual void SetIndexValue(int index, object value) { }

        protected virtual object GetIndexValue(int index) => default;

        /// <summary>
        /// 方法参数值集合(只读集合)
        /// </summary>
        public virtual object[] ParamValues => new object[] { };

        #endregion

        #region TryGetValue       

        private bool TestName(string paramName, out int index)
        {
            return NameMap.TryGet(paramName, out index);
        }

        private bool TestIndex(int paramIndex)
        {
            return paramIndex < NameMap.ParamNames.Length;
        }

        /// <summary>
        /// 尝试返回指定名称的参数值
        /// </summary>
        /// <typeparam name="T">参数类型</typeparam>
        /// <param name="paramName">参数名称</param>
        /// <param name="defaultValue">默认值</param>
        public T TryGetValue<T>(string paramName, T defaultValue = default)
        {
            return TestName(paramName, out var index) ? (T)GetIndexValue(index) : defaultValue;
        }

        /// <summary>
        /// 尝试返回指定名称的参数值
        /// </summary>
        /// <param name="paramName">参数名称</param>
        public object TryGetValue(string paramName) => TryGetValue<object>(paramName);

        /// <summary>
        /// 尝试返回指定索引位置的参数值
        /// </summary>
        /// <typeparam name="T">参数类型</typeparam>
        /// <param name="paramIndex">参数名称</param>
        /// <param name="defaultValue">默认值</param>
        public T TryGetValue<T>(int paramIndex, T defaultValue = default)
        {
            return TestIndex(paramIndex) ? (T)GetIndexValue(paramIndex) : defaultValue;
        }

        /// <summary>
        /// 尝试返回指定索引位置的参数值
        /// </summary>
        /// <param name="paramIndex">参数名称</param>
        public object TryGetValue(int paramIndex) => TryGetValue<object>(paramIndex);

        #endregion

        #region TrySetValue

        /// <summary>
        /// 尝试设置指定名称的参数值
        /// </summary>
        /// <param name="paramName">参数名称</param>
        /// <param name="value">值</param>
        public bool TrySetValue(string paramName, object value)
        {
            var isok = TestName(paramName, out var index);
            if (isok) SetIndexValue(index, value);
            return isok;
        }

        /// <summary>
        /// 尝试设置指定索引位置的参数值
        /// </summary>
        /// <param name="paramIndex">参数名称</param>
        /// <param name="value">值</param>
        public bool TrySetValue(int paramIndex, object value)
        {
            var isok = TestIndex(paramIndex);
            if (isok) SetIndexValue(paramIndex, value);
            return isok;
        }

        #endregion

        protected IndexOutOfRangeException GetError(int index)
        {
            var err = $"试图访问索引超出界限的参数时引发的异常\r\nIndex = {index}\r\nMethodInfo={MethodInfo}";
            return new IndexOutOfRangeException(err);
        }

        protected string GetCallPtah(int index) => $"{MethodInfo}.Parameter[{index}]";
    }

    public class MethodArgs<T1> : MethodArgs
    {
        protected T1 t1;

        public MethodArgs(Lazy<AopParamInfo> aopParam, object sender) : base(aopParam, sender) { }

        public void SetValue(T1 t1) => this.t1 = t1;

        public override object[] ParamValues => new object[] { t1 };

        protected override object GetIndexValue(int index)
        {
            return 0 == index ? t1 : base.GetIndexValue(index);
        }

        protected override void SetIndexValue(int index, object value)
        {
            if (0 == index)
            {
                t1 = (T1)value;
                return;
            }
            base.SetIndexValue(index, value);
        }

        public void OutValue(out T1 t1) => t1 = this.t1;
    }
}